package game.games.gol;

import game.GameConfig;
import game.INotScrollable;
import game.console.GameConsole;
import game.console.IConsoleCommand;
import game.coord.IterablePoint;
import game.gfx.Fullscreen;
import game.gfx.cellmap.AbstractCellMapGame;
import game.gfx.cellmap.ICellSizeChangeListener;
import game.util.Logger;
import game.util.Util;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.swing.SwingUtilities;

@Fullscreen
public class GameOfLife extends AbstractCellMapGame<GameOfLifeCellMap> implements INotScrollable, ICellSizeChangeListener
{
    private int                          generation           = 0;
    private int                          livingCells          = 0;
    private int                          livingCellsIdentical = 0;
    private volatile int                 nextLivingCells      = 15000;
    
    private GameOfLifeRule               rule                 = GameOfLifeRule.DEFAULT;
    private Map<Integer, GameOfLifeRule> zones                = new HashMap<Integer, GameOfLifeRule>();
    
    private ColorSet                     colorSet             = ColorSet.DEFAULT;
    private boolean                      CREATE_LIVE_MODE     = false;
    private boolean                      RESET_WHEN_NOACTION  = true;
    private boolean                      isResurecting;
    
    public GameOfLife()
    {
        super(false, new GameOfLifeCellMap());
        getGameListener().addKeyListener(new KeyAdapter() 
        {
            private boolean IGNORE_KEYBOARD = false;
            
            @Override
            public void keyPressed(KeyEvent e)
            {
                if (IGNORE_KEYBOARD)
                {
                    e.consume();
                }
                else
                {
                    IGNORE_KEYBOARD = true;
                    final GameConsole console = getConsole();
                    if (console.isOpen() == false)
                    {
                        switch (e.getKeyCode())
                        {
                            case KeyEvent.VK_P:
                            {
                                if (isPaused())
                                {
                                    resume();
                                }
                                else if (isRunning())
                                {
                                    pause();
                                }
                                break;
                            }
                            case KeyEvent.VK_S:
                            {
                                reset();
                                restart();
                                break;
                            }
                            case KeyEvent.VK_R:
                            {
                                restart();
                                break;
                            }
                            case KeyEvent.VK_ESCAPE:
                            {
                                reset();
                                break;
                            }
                            default:
                            {
                                getMap().getKeyAdapter().keyPressed(e);
                                break;
                            }
                        }
                    }
                    IGNORE_KEYBOARD = false;
                }
            }
        });
        getGameListener().addMouseListener(new MouseAdapter() 
        {
            @Override
            public void mouseClicked(MouseEvent e)
            {
                if (SwingUtilities.isRightMouseButton(e))
                {
                    CREATE_LIVE_MODE = CREATE_LIVE_MODE == false;
                    Logger.debug("CREATIVE_LIVE_MODE ", CREATE_LIVE_MODE);
                }
            }
        });
        getGameListener().addMouseMotionListener(new MouseMotionAdapter() 
        {
            @Override
            public void mouseMoved(MouseEvent e)
            {
                if (CREATE_LIVE_MODE && isReset() == false)
                {
                    try
                    {
                        Logger.trace("SURVIVING: ", getMousePoint());
                        Cell cell = getMap().getCells().getValue(getMousePoint());
                        cell.setAlive(true);
                        cell.setSurviving(true);
                    }
                    catch (IllegalArgumentException ex)
                    {
                        ex.printStackTrace();
                    }
                }
            }
        });
    }
    
    @Override
    protected void initConfig()
    {
        getConfig().setTrue(GameConfig.ALLOW_FPSCHANGE);
        for (IConsoleCommand c : GameOfLifeCommands.getCommands(this))
        {
            getConsole().addConsoleCommand(c);
        }
    }
    
    public void setRule(String rule)
    {
        setRule(new GameOfLifeRule(rule));
    }
    
    public void setRule(GameOfLifeRule rule)
    {
        setRule(0, rule);
    }
    
    public void setRule(int zone, GameOfLifeRule newRule)
    {
        pause();
        try
        {
            if (zone == 0)
            {
                rule = newRule;
            }
            else
            {
                zones.put(zone, newRule);
            }
        }
        catch (Exception ex)
        {
            Logger.error(ex);
        }
        finally
        {
            resume();
        }
    }
    
    public void setRule(int zone, String rule)
    {
        GameOfLifeRule newRule = new GameOfLifeRule(rule);
        setRule(zone, newRule);
    }
    
    public void setNextLivingCells(int livingCells)
    {
        if (livingCells > getMap().getCells().countValues())
        {
            livingCells = getMap().getCells().countValues();
        }
        this.nextLivingCells = livingCells;
    }
    
    public void setProzentLivingCells(int prozent)
    {
        if (prozent >= 100)
        {
            setNextLivingCells(getMap().getCells().countValues());
        }
        else
        {
            setNextLivingCells((getMap().getCells().countValues() / 100) * prozent);
        }
    }
    
    private static Thread AddLivingCellsThreadInstance = null;
    
    private class AddLivingCellsThread implements Runnable
    {
        private volatile List<Point> livingCells = Collections.synchronizedList(new ArrayList<Point>());
        
        private class RandomLivingCellThread implements Runnable
        {
            
            @Override
            public void run()
            {
                // Lebende Zellen generieren
                Random RANDOM = new Random(System.nanoTime());
                do
                {
                    Point index = new Point(RANDOM.nextInt(getMap().getMapSize().width), RANDOM.nextInt(getMap().getMapSize().height));
                    if (livingCells.contains(index) == false)
                    {
                        livingCells.add(index);
                    }
                }
                while (livingCells.size() < nextLivingCells);
            }
        }
        
        private final int maxThreads = 8;
        
        @Override
        public void run()
        {
            long startTime = System.nanoTime();
            do
            {
                List<Thread> threads = new ArrayList<Thread>();
                while (isReset() && threads.size() < maxThreads)
                {
                    Thread t = new Thread(new RandomLivingCellThread(), "RandomLivingCellsThread " + threads.size());
                    t.start();
                    threads.add(t);
                }
                for (Thread thread : threads)
                {
                    while (isReset() && thread.isAlive())
                    {
                        // return
                        try
                        {
                            thread.join();
                        }
                        catch (InterruptedException e)
                        {
                            //
                        }
                    }
                }
                Logger.trace(Thread.currentThread().getName(), ": ", livingCells.size(), " cells of ", nextLivingCells, " found");
            }
            while (isReset() && livingCells.size() < nextLivingCells);
            while (isReset() && livingCells.size() > nextLivingCells)
            {
                // return
                livingCells.remove(0);
            }
            Logger.info(livingCells.size(), " living cells found (", Util.nanoToSec(System.nanoTime() - startTime), " sec)");
            // reset count
            for (Point index : livingCells)
            {
                // return
                if (isReset() == false)
                {
                    break;
                }
                Cell cell = getMap().getCells().getValue(index);
                cell.setAlive(true);
                cell.setSurviving(true);
            }
        }
    }
    
    private void addLivingCells()
    {
        if (nextLivingCells <= 0)
        {
            return;
        }
        if (AddLivingCellsThreadInstance == null)
        {
            AddLivingCellsThreadInstance = new Thread(new AddLivingCellsThread(), "AddLivingCellsThread");
            AddLivingCellsThreadInstance.start();
        }
        while (AddLivingCellsThreadInstance != null && AddLivingCellsThreadInstance.isAlive())
        {
            try
            {
                AddLivingCellsThreadInstance.join();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        AddLivingCellsThreadInstance = null;
    }
    
    @Override
    protected void onReset()
    {
        long startTime = System.nanoTime();
        // init
        getMap().addCellSizeChangeListener(this);
        getMap().setCellSizes(1, 3, 5, 7, 10, 12, 15, 18, 20, 25);
        // Anzahl Zellen
        Dimension size = getFrameSize();
        Dimension maxSize = getMap().calcMaxSize(size);
        // Liste generieren
        getMap().setMapSize(maxSize);
        if (nextLivingCells > getMap().getCells().countValues())
        {
            setProzentLivingCells(25);
        }
        Logger.debug("initGeneration(", maxSize.getWidth(), "/", maxSize.getHeight(), ") complete, ");
        Logger.debug(getMap().getCells().countValues(), " cells created (", Util.nanoToSec(System.nanoTime() - startTime), " sec)");
    }
    
    @Override
    protected void onStart()
    {
        Logger.trace("start()");
        long startTime = System.nanoTime();
        addLivingCells();
        generation = 0;
        Logger.debug("resetGeneration(", getMap().getCells().countValues(), ") (", Util.nanoToSec(System.nanoTime() - startTime), " sec)");
    }
    
    public void setColorSet(ColorSet colorSet)
    {
        this.colorSet = colorSet;
    }
    
    public ColorSet getColorSet()
    {
        if (colorSet == null)
        {
            colorSet = ColorSet.DEFAULT;
        }
        return colorSet;
    }
    
    @Override
    public BufferedImage getImage()
    {
        Dimension mapSize = getMap().getCellMapSize();
        // image
        BufferedImage image = new BufferedImage(mapSize.width, mapSize.height, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g = image.createGraphics();
        for (IterablePoint<Cell> i = getMap().getCells().getIterator(); i.hasNext();)
        {
            Point position = i.getCurrentPoint();
            Cell cell = i.next();
            Color color = getColorSet().getColor(cell.isAlive(), cell.isSurviving());
            getMap().fillRect(g, position, color);
            if (isPaused() == false)
            {
                // next round
                cell.setAlive(cell.isSurviving());
            }
        }
        if (isPaused() == false)
        {
            generation++;
        }
        return image;
    }
    
    @Override
    public void doNext()
    {
        int alive = 0;
        isResurecting = false;
        for (IterablePoint<Cell> i = getMap().getCells().getIterator(); i.hasNext();)
        {
            Cell cell = i.next();
            int livingNeighbors = cell.countLivingNeighbors();
            // get zone rule
            GameOfLifeRule rule = null;
            Point cp = i.getCurrentPoint();
            int factorX = getMap().getMapSize().width  / 3;
            int factorY = getMap().getMapSize().height / 3;
            if (cp.y < factorY)
            {
                if (cp.x < factorX)
                {
                    rule = zones.get(1);
                }
                else if (cp.x < factorX * 2)
                {
                    rule = zones.get(2);
                }
                else
                {
                    rule = zones.get(3);
                }
            }
            else if (cp.y < factorY * 2)
            {
                if (cp.x < factorX)
                {
                    rule = zones.get(4);
                }
                else if (cp.x < factorX * 2)
                {
                    rule = zones.get(5);
                }
                else
                {
                    rule = zones.get(6);
                }
            }
            else
            {
                if (cp.x < factorX)
                {
                    rule = zones.get(7);
                }
                else if (cp.x < factorX * 2)
                {
                    rule = zones.get(8);
                }
                else
                {
                    rule = zones.get(9);
                }
            }
            if (rule == null)
            {
                rule = this.rule;
            }
            cell.setSurviving(rule.isSurviving(cell.isAlive(), livingNeighbors));
            if (cell.isAlive())
            {
                alive++;
            }
            // check isResurecting
            if (cell.isAlive() == false && cell.isSurviving())
            {
                isResurecting = true;
            }
        }
        if (alive == livingCells)
        {
            livingCellsIdentical++;
        }
        else
        {
            livingCellsIdentical = 0;
        }
        livingCells = alive;
        setInfo(isResurecting + " - Generation " + generation + "; " + livingCells + " Living Cells");
        if (RESET_WHEN_NOACTION)
        {
            if (livingCells <= 0)
            {
                Logger.info("No more living cells");
                restart();
            }
            else if (livingCellsIdentical > 50)
            {
                restart();
            }
        }
    }
    
    @Override
    public void recalcSize(Dimension windowSize)
    {
        Dimension maxXY = getMap().calcMaxSize(windowSize);
        if (maxXY.equals(getMap().getMapSize()) == false)
        {
            getMap().setMapSize(maxXY);
            int prozentLivingCells = 30;
            if (getMap().getCellSize() < 10)
            {
                prozentLivingCells = 5;
            }
            setProzentLivingCells(prozentLivingCells);
        }
    }
    
    @Override
    public void cellSizeChanged(int newCellSize)
    {
        restart();
    }
    
    public static void main(String[] args)
    {
        Thread t = new Thread(new GameOfLife(), "GameOfLife");
        t.start();
    }
}
